/**
 * creation: 16-mei-2007
 */
package org.jfonia.rhythm;

import org.jfonia.staffnotation.BasicTimeSignature;
import org.jfonia.util.JFoniaException;


/**
 * @author Wijnand
 *
 */
public class RhythmNotation
{
	private int tickCountPerWhole = 4;	// default: one tick per quarter

	/**
	 * Calculates the number of ticks for a given notevalue (duration), given as a fraction nom/denom
	 * (e.g. nom=1, denom=4 denotes a quarter)
	 * @param nom 
	 * @param denom pre:denom!=0
	 * @return number of ticks 
	 * @throws JFoniaException if notevalue is not representable using current tickCountPerWhole
	 */
	// @Override
	public int calcTickCount(int nom, int denom) throws JFoniaException
	{
		assert(denom != 0);
		int p = nom*tickCountPerWhole;
		if (p % denom != 0)
			throw new JFoniaException("value "+nom+"/"+denom +" is not representable with current tickCountPerWhole");
		return p/denom;
	}
	
	/**
	 * Calculates the reciproque of the value corresponding to a given number of ticks
	 * @param tickCount pre:tickCount!=0
	 * @return reciproque value (1=whole, 2=half, 4=quarter, ...; also: 12=eight-triplet, ...)
	 * @throws JFoniaException if tickCount is not representable as a pure reciproque with current tickCountPerWhole
	 */
	public int calcReciproqueValue(int tickCount) throws JFoniaException
	{
		assert(tickCount != 0);
		if (tickCountPerWhole%tickCount != 0)
			throw new JFoniaException("tickCount "+tickCount+" is not representable as a pure reciproque with current tickCountPerWhole");
		return tickCountPerWhole/tickCount;
	}
	
	
	/**
	 * calculate the (integer) base-2 logarithm of a given number r if it is a natural power of two.
	 * <br>Note: exp2() is the inverse function.
	 * <br>Example: log2(1)->0, log2(2)->1, log2(4)->2, ... log2(3)->exception
	 * @param r pre:r>0
	 * @return base-2 logarithm
	 * @throws JFoniaException if r is not a pure power of 2
	 */
	public static int log2(int r) throws JFoniaException	// r is usually a reciproque value
	{
		// whole  : 1 -> 0
		// half   : 2 -> 1
		// quarter: 4 -> 2
		// 8triplet:12 -> exception
		assert(r > 0);
		int result = 0;
		while (r > 1)
		{
			if (r%2 != 0)
				throw new JFoniaException(""+r+ " is not a power of 2");
			r /= 2;
			result++;
		}
		return result;
	}

	/**
	 * calculate the integer base-2 logarithm of a given number r, rounded down.
	 * <br>Example: log2(1)->0, log2(2)->1, log2(3)->1, log2(4)->2, ... 
	 * @param r pre:r>0
	 * @return base-2 logarithm
	 */
	public static int lowerLog2(int r) 
	{
		assert(r > 0);
		int result = 0;
		while (r > 1)
		{
			r /= 2;
			result++;
		}
		return result;
	}

	/**
	 * checks whether a given number r is a natural power of 2 (r=1,2,4, ...). This means log2(r) will not throw an exception.
	 * @param r pre:r>0
	 * @return true if r is a natural power of 2
	 */
	public static boolean isExp2(int r)
	{
		assert(r > 0);
		while (r > 1)
		{
			if (r%2 != 0)
				return false;
			r /= 2;
		}
		return true;
	}
	
	/**
	 * Calculates 2^k if it is an integer 
	 * <br>Note: exp2() is the inverse function.
	 * @param k pre:k>=0
	 * @return 2^k
	 */
	public static int exp2(int k)  // k is usually a log2 of a reciproque value
	{
		assert(k >= 0);
		int result = 1;
		while (k > 0)
		{
			k--;
			result *= 2;
		}
		return result;
	}
	
	/**
	 * Constructs a BasicTimeSignature from a metricInfo (using only beatCount and beatTickCount), according to tickCountPerWhole.
	 * E.g. 2,30 -> 2/4 if tickCountPerWhole==120.
	 * If the beat-duration corresponds to a dotted note, then 6/8-like notation is used.
	 * E.g. 2,45 -> 6/8 if tickCountPerWhole==120.
	 *   
	 * @param metricInfo
	 * @return BasicTimeSignature
	 * @throws JFoniaException if not expressible
	 */
	public BasicTimeSignature makeBasicTimeSignature(MetricInfo metricInfo) throws JFoniaException
	{
		int nominator;
		int denominator;
		if (tickCountPerWhole % metricInfo.getBeatTickCount() == 0)
		{
			nominator = metricInfo.getBeatCount();
			denominator = tickCountPerWhole / metricInfo.getBeatTickCount();
			if (isExp2(denominator))
				return new BasicTimeSignature(nominator, denominator);
		}
		else if ((tickCountPerWhole*3) % metricInfo.getBeatTickCount() == 0)
		{
			nominator = metricInfo.getBeatCount() * 3;
			denominator = (tickCountPerWhole*3) / metricInfo.getBeatTickCount();
			if (isExp2(denominator))
				return new BasicTimeSignature(nominator, denominator);
		}
	
		throw new JFoniaException("metricInfo with beat-duration "+metricInfo.getBeatTickCount()+" is not expressible as BasicTimeSignature with current tickCountPerWhole");
		
	}
	
	/**
	 * @return number of ticks per whole note
	 */
	public int getTickCountPerWhole()
	{
		return tickCountPerWhole;
	}

	/**
	 * @param tickCountPerWhole number of ticks per whole note
	 */
	public void setTickCountPerWhole(int tickCountPerWhole)
	{
		this.tickCountPerWhole = tickCountPerWhole;
	}
	
}
